﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Browis.Package;
using ICSharpCode.SharpZipLib.Tar;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Specialized;
using ICSharpCode.SharpZipLib.GZip;
using System.Xml.Serialization;
using System.Xml;
using log4net;
using System.Data.Common;
using System.Data;
using ICSharpCode.SharpZipLib.Core;
using System.Configuration;

namespace Browis.Cds.Plugin
{
    public static class Program
    {
        static void Main(string[] args)
        {
            log4net.Config.XmlConfigurator.Configure();
            ILog log = LogManager.GetLogger("Program");
            //string fileName = "2012051019343051.cdp";
            //string fileName = "2012042814220657.cdp";
            Browis.Package.Package package = null;
            try
            {
                //package = Browis.Package.Package.Open(Path.Combine(@"D:\Temp\nozip", fileName), new FileStream(Path.Combine(@"D:\Temp\nozip", fileName), FileMode.Open, FileAccess.Read));

                //using (SectionStream ss = package.LookupSection("AUTH"))
                //{
                //    if (CheckUserPassword(ss) == false)
                //        return;
                //}

                string[] fileNames = Directory.GetFiles(@"G:\123", "*.cdp", SearchOption.TopDirectoryOnly);
                foreach (string fileName in fileNames)
                {
                    UTarDataPak unpacker = new UTarDataPak();

                    //保留解压文件根目录
                    NameValueCollection nv = new NameValueCollection();
                    nv.Add("RootDirectory", @"G:\123\temp");
                    //保留入库服务的配置地址
                    //nv.Add("ConnectionString", "Data Source=CEI-ZHW;Initial Catalog=invest;Persist Security Info=False;User ID=sa;Password=sa");
                    //nv.Add("ProviderName", "System.Data.SqlClient");
                    //nv.Add("DBCommandName", "SQLSERVER");


                    unpacker.Initialize(nv, package);
                    //log.InfoFormat("包文件{0}开始解包！", package.FileName);
                    //unpacker.Unpack();
                    //unpacker.Final();
                    //DbProviderFactory providerFactory = DbProviderFactories.GetFactory(nv["ProviderName"]);
                    //using (IDbConnection conn = providerFactory.CreateConnection())
                    //{
                    //    conn.ConnectionString = nv["ConnectionString"];
                    //    conn.Open();
                    //    unpacker.UnDatapack(Path.Combine(@"D:\Temp\nozip", fileName), conn);
                    //}
                    unpacker.UnZipFile(Path.Combine(@"G:\Temp\包文件\已压缩", fileName));
                    //log.InfoFormat("包文件{0}解包完成！", package.FileName);
                }


            }
            catch (Exception e)
            {
                log.WarnFormat("包文件{0}解包过程出现异常：{1}", package.FileName, e.Message);
                //System.Console.WriteLine(e);
            }
            finally
            {
                if (package != null)
                    package.Close();

            }
        }
    }

    public class UTarDataPak : IUnPack
    {
        /// <summary>
        /// Log
        /// </summary>
        private ILog log = null;
        private Browis.Package.Package pkg;
        private string sRoot = "C:";
        //private Dictionary<String, DateTime> snapshot;

        /// <summary>
        /// 解包后，需要更新的数据库配置列表
        /// </summary>
        private ConnectionStringSettingsCollection unPakConnections;

        #region IUnPack 成员

        public void Initialize(NameValueCollection config, Browis.Package.Package package)
        {
            log = LogManager.GetLogger("UnPack");
            if (!string.IsNullOrEmpty(config["RootDirectory"]))
            {
                sRoot = config["RootDirectory"];
            }
            unPakConnections = ConfigurationManager.ConnectionStrings;

            pkg = package;
        }

        private Boolean Validate(TarEntry entry)
        {
            String fileName = Path.Combine((String)pkg.GetMetaValue("Path"), entry.Name).Replace('/', Path.DirectorySeparatorChar);
            if (fileName[0] == Path.DirectorySeparatorChar)
            {
                fileName = fileName.Substring(1);
            }
            fileName = Path.Combine(sRoot, fileName);

            if (File.Exists(fileName) == false)
            {
                return true;
            }
            else
            {
                log.WarnFormat("文件【{0}】已存在。", fileName);
            }

            if (entry.ModTime.CompareTo(File.GetLastWriteTime(fileName)) > 0)
            {
                log.InfoFormat("最新文件【{0}】大于当前文件最后更新时间，将覆盖现有文件，重新解包。", fileName);
                return true;
            }
            else
            {
                log.WarnFormat("最新文件【{0}】小于当前文件最后更新时间，不再重复解包。", fileName);
            }               

            return false;
        }

        public void Unpack()
        {
            SectionStream ss = pkg.LookupSection("DATA");
            if (ss == null)
                return;

            TarInputStream tar = new TarInputStream(ss);

            String path = ((String)pkg.GetMetaValue("Path")).Replace('/', Path.DirectorySeparatorChar);
            if (path[0] == Path.DirectorySeparatorChar)
            {
                path = path.Substring(1);
            }
            string sRootPath = Path.Combine(sRoot, path);

            while (true)
            {
                TarEntry entry = tar.GetNextEntry();
                if (entry == null)
                    break;

                String name = entry.Name.Replace('/', Path.DirectorySeparatorChar);
                if (name[0] == Path.DirectorySeparatorChar)
                {
                    name = name.Substring(1);
                }
                path = Path.Combine(path, name);
                name = Path.Combine(sRootPath, name);

                if (entry.IsDirectory == true)
                {
                    if (Directory.Exists(name) == false)
                        Directory.CreateDirectory(name);
                }
                else
                {
                    if (Validate(entry) == true)
                    {
                        String directoryName = Path.GetDirectoryName(name);

                        if (Directory.Exists(directoryName) == false)
                            Directory.CreateDirectory(directoryName);

                        try
                        {
                            FileStream fs = new FileStream(name, FileMode.Create, FileAccess.Write);
                            tar.CopyEntryContents(fs);
                            fs.Close();

                            File.SetCreationTime(name, entry.ModTime);
                            File.SetLastWriteTime(name, entry.ModTime);

                            log.InfoFormat("解压文件【{0}】完成。", Path.GetFileName(name));

                            UnZipFile(name);
                        }
                        catch
                        { throw; }
                        finally
                        {
                            if (File.Exists(name))
                            {
                                File.Delete(name);
                                log.DebugFormat("文件【{0}】已经被删除。", name);
                            }
                        }
                    }
                    else
                    {
                        log.WarnFormat("包文件【{0}】未解包。", entry.Name);
                    }
                }
            }

            ss.Close();
        }

        public void Final()
        {
        }

        public void UnZipFile(string fileName)
        {
            Stream memoryStream = null;
            try
            {
                GZipInputStream gzip = new GZipInputStream(new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite));
                gzip.IsStreamOwner = true;

                memoryStream = new MemoryStream();

                int size = 1024 * 32;//指定压缩块的大小，一般为2048的倍数     
                byte[] writeData = new byte[size];//指定缓冲区的大小     

                StreamUtils.Copy(gzip, memoryStream, writeData);
                gzip.Close();

                memoryStream.Seek(0, SeekOrigin.Begin);
                DataPackage pak = DataPackage.Open(fileName, memoryStream);
                bool cancelTransaction = pak.CancelTransaction;

                bool dbExist = false;
                foreach (ConnectionStringSettings connConfig in unPakConnections)
                {
                    memoryStream.Seek(pak.HeaderLength, SeekOrigin.Begin);
                    string[] strPs = connConfig.ProviderName.Split(new char[] { '/' });
                    string providerName = strPs[0];
                    string dbCommandName = "SQLSERVER";
                    if (strPs.Length == 2)
                    {
                        dbCommandName = strPs[1];
                    }
                    string[] dbNames = connConfig.Name.Split(new char[]{'/'});
                    string dbName = dbNames[dbNames.Length - 1];

                    if (string.Compare(pak.DBName, dbName, true) == 0)
                    {
                        dbExist = true;
                        DbProviderFactory providerFactory = DbProviderFactories.GetFactory(providerName);
                        using (IDbConnection conn = providerFactory.CreateConnection())
                        {
                            conn.ConnectionString = connConfig.ConnectionString;
                            conn.Open();

                            IDbTransaction tran = null;
                            if (!cancelTransaction)
                            {
                                tran = conn.BeginTransaction();
                            }
                            
                            try
                            {
                                UnDatapack(pak, memoryStream, dbCommandName, conn, providerName, tran);
                                if (!cancelTransaction)
                                {
                                    tran.Commit();
                                }
                            }
                            catch(Exception exp)
                            {
                                if (!cancelTransaction)
                                {
                                    tran.Rollback();
                                }
                                log.Error(string.Format("执行解包命令失败，", exp.Message), exp);
                                throw; 
                            }                            
                        }
                    }
                }
                if (!dbExist)
                {
                    log.ErrorFormat("目标数据库【{0}】不存在，解包【{1}】失败。", pak.DBName.Replace("\0", string.Empty), Path.GetFileName(fileName));
                }
            }
            catch (Exception exp)
            {
                log.ErrorFormat("解包【{0}】失败，{1}", fileName, exp.Message);
                log.Debug(string.Format("解包【{0}】失败，{1}", fileName, exp.Message), exp);
                throw;
            }
            finally
            {
                if (memoryStream != null)
                {
                    memoryStream.Close();
                    memoryStream.Dispose();
                }
            }
        }

        public void UnDatapack(DataPackage pak, Stream reader, string dbCommandName, IDbConnection conn, string providerName, IDbTransaction tran)
        {
            try
            {
                while (reader.Position < reader.Length)
                {
                    //顺序读取Command流
                    SectionStream ssCommand = pak.OpenCommand();

                    if (ssCommand == null)
                    {
                        log.Info("命令不存在，退出。");
                        continue;
                    }

                    if (ssCommand.Length == 0)
                    {
                        log.Debug("命令无数据，退出。");
                        continue;
                    }

                    log.DebugFormat("开始执行命令【{0}】...", ssCommand.Name);

                    DataCommand command = new DataCommand();

                    //读取数据库字符串和参数流
                    SectionStream ssDatabase = SectionStream.Lookup(ssCommand, dbCommandName, log);

										if (ssDatabase == null)
										{
											log.Debug(string.Format("目标命令【{0}】不存在，退出。", dbCommandName));
										}

                    //读取SQL流
                    SectionStream ssSql = SectionStream.Lookup(ssDatabase, UTarDataPakCommandName.SQL, log);

                    //读取插入SQL语句
                    command.Text = ReadSQL(ssSql);

                    //读取更新SQL语句
                    command.ReserveText = ReadSQL(ssSql);

                    BinaryFormatter bf = new BinaryFormatter();

                    //读取参数信息流
                    SectionStream ssParameter = SectionStream.Lookup(ssDatabase, UTarDataPakCommandName.Parameter, log);
                    if (ssParameter != null && ssParameter.Length > 0)
                    {
                        command.Parameters = (List<ParameterInfo>)bf.Deserialize(ssParameter);
                    }                    

                    //读取数据包流
                    SectionStream ssData = SectionStream.Lookup(ssCommand, UTarDataPakCommandName.Data, log);

                    if (ssData != null && ssData.Length > 0)
                    {
                        //读取数据包头信息流
                        SectionStream ssDataHeader = SectionStream.Lookup(ssData, UTarDataPakCommandName.DataHeader, log);
                        if (ssDataHeader != null && ssDataHeader.Length > 0)
                        {
                            command.CommandValues = (List<CommandValue>)bf.Deserialize(ssDataHeader);
                        }

                        //读取Data Values 值
                        command.DataReader = ssData;
                    }

                    command.ProviderName = providerName;
                    long effectedCount = 0;
                    long nMGEffectedCount = 0;
                    //对于不是批量插入数据的，正常执行
                    if (command.Text.Trim() != "执行批量插入命令！")
                    {
                        //执行
                        effectedCount = command.Execute(conn, tran, ssCommand.Name, pak.DBName);

                        if (effectedCount < 0)
                        {
                            effectedCount = 0;
                        }
                    }
                    //批量插入数据，首先生成导入文件
                    else
                    {
                        bool bHaveTran = false;
                        //批量插入不能再事务中，首先结束事务
                        if (tran != null)
                        {
                            tran.Commit();
                            bHaveTran = true;
                        }
                        if (command.ReserveText.StartsWith("dbo.", StringComparison.OrdinalIgnoreCase))
                        {
                            int count = command.ReserveText.Length;
                            command.ReserveText = command.ReserveText.Substring(4, count - 4);
                        }

                        GetImportDataFile getF = new GetImportDataFile();
                        //对于SQL Server,默认采用BulkCopy方式。此时ReserveText节点中保存目标表名（包括架构）
                        if (conn.GetType() == typeof(GBase.Data.GBaseClient.GBaseConnection) && (string.IsNullOrEmpty(ConfigurationManager.AppSettings["UseBulkInsert"]) || ConfigurationManager.AppSettings["UseBulkInsert"].ToLower() == "false"))
                        {
                            BulkCopy.GBaseBulkCopy bulkCopy = new BulkCopy.GBaseBulkCopy((GBase.Data.GBaseClient.GBaseConnection)conn);
                            //using (System.Data.SqlClient.SqlBulkCopy bulkCopy = new System.Data.SqlClient.SqlBulkCopy((System.Data.SqlClient.SqlConnection)conn))
                            //{
                            bulkCopy.BulkCopyTimeout = 0;
                            bulkCopy.DestinationTableName = command.ReserveText;

                            BulkCopy.GBaseBulkCopy nMGBulkCopy = null;
                            IDictionary<string, DataRow> refIndicator = null;
                            if (string.Compare(pak.DBName,"新统计库",true) == 0 && NMGCommand.CheckNMGPakBulkCommand(ssCommand.Name))
                            {
                                nMGBulkCopy = new BulkCopy.GBaseBulkCopy(ConfigurationManager.ConnectionStrings["内蒙基础库"].ConnectionString);
                                nMGBulkCopy.BulkCopyTimeout = 0;
                                nMGBulkCopy.DestinationTableName = NMGCommand.GetBulkDestinationTableName(ssCommand.Name);
                            }
                            try
                            {
                                while (command.DataReader.Position < command.DataReader.Length)
                                {
                                    // Write from the source to the destination.
                                    DataTable dt = getF.GetDataTable(command);
                                    bulkCopy.Upload(dt);
                                    effectedCount += dt.Rows.Count;

                                    if (nMGBulkCopy != null)
                                    {
                                        DataTable nMGDT = NMGCommand.GetDataTable(nMGBulkCopy, dt, ssCommand.Name, ref refIndicator);
                                        nMGBulkCopy.Upload(nMGDT);
                                        nMGEffectedCount += nMGDT.Rows.Count;

                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                log.ErrorFormat("批量导入时出现错误：{0}", ex);
                            }
                            finally
                            {
                                if (nMGBulkCopy != null)
                                {
                                    nMGBulkCopy.Close();
                                    log.InfoFormat("【内蒙基础库】：执行结束，来自解包命令【{0}】，影响行数【{1}】", ssCommand.Name, nMGEffectedCount);
                                }
                            }

                            if (refIndicator != null)
                            {
                                int indicatorCount = NMGCommand.UpdateIndicator(refIndicator);
                                log.InfoFormat("【内蒙基础库】：指标表【tbIndicator】执行结束，来自解包命令【{0}】，影响行数【{1}】", ssCommand.Name, indicatorCount);
                            }
                            //}
                        }
                        else
                        {
                            string sFile = getF.GetImportFile(command, sRoot, ssCommand.Name);
                            effectedCount = command.BulkExecute(conn, sFile);
                        }
                        if (bHaveTran)
                        {
                            tran = conn.BeginTransaction();
                        }
                    }
                    log.InfoFormat("命令【{0}】执行结束，影响行数【{1}】", ssCommand.Name, effectedCount);
                }
            }
            catch (Exception exp)
            {
                log.ErrorFormat("解析包命令失败，", exp.Message);
                log.Debug(string.Format("解析包命令失败，", exp.Message), exp);
                throw;
            }
        }

        /// <summary>
        /// 读取SQL语句
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private string ReadSQL(Stream reader)
        {
            //读取SQL语句长度
            byte[] bLength = new byte[sizeof(UInt32)];
            reader.Read(bLength, 0, bLength.Length);
            
            //SQL语句长度
            uint iLength = BitConverter.ToUInt32(bLength, 0);

            byte[] bSQL = new byte[iLength];
            reader.Read(bSQL, 0, bSQL.Length);

            //获取SQL
            //string sText = Encoding.UTF8.GetString(bSQL);
            string sText = Encoding.GetEncoding("gb2312").GetString(bSQL);

            //string replaceText = sText.Replace

            return sText;
        }

        #endregion
    }

    /// <summary>
    /// 解包命令节点名称（ID）
    /// </summary>
    public class UTarDataPakCommandName
    {
        public const string Data = "Data";
        public const string SQL = "SQL";
        public const string Parameter = "Parameter";
        public const string DataHeader = "DataHeader";
    }
}
